#ifndef PW_PBL_FLOATINT_HPP
/// 表示浮点值的整形
#define PW_PBL_FLOATINT_HPP

#include "Meta/Auto.hpp"
#include "Math.hpp"
#include "CharConv.hpp"
#include <charconv>
#include <concepts>
#include <cstdint>
#include <cstdlib>
#include <cmath>
#include <limits>
#include <numeric>
#include <ratio>

namespace pw_pbl
{
	namespace _floatint
	{
		template <std::integral T>
		struct div_t
		{
			T quot;
			T rem;
		};

		template <std::integral T>
		constexpr div_t<T> div(T a, std::type_identity_t<T> b) { return {.quot = T(a / b), .rem = T(a % b)}; }

		/// a >= 0, b > 0
		template <std::integral T>
		constexpr auto divRound(T a, T b)
		{
			auto result = div(a, b);
			return result.rem >= (b + 1) >> 1 ? result.quot + 1 : result.quot;
		}

		/// a * num / den,  num <= den
		template <std::integral T>
		constexpr auto mulDiv(T v, T num, T den)
		{
			auto result = div(v, den);
			auto r2 = div(result.rem * num, den);
			result.quot = result.quot * num + r2.quot;
			result.rem = r2.rem;
			return result;
		}

		/// a * b / c, 条件：a 和 b 均不为0且可能大于c,  最终结果已知不溢出，但a * b可能溢出
		template <std::integral T>
		constexpr auto bigMulDiv(T a, T b, T c)
		{
			auto ra = div(a, c);
			auto rb = div(b, c);
			T rem2;
			if (Math::MulOverflow(ra.rem, rb.rem, &rem2))
				return div_t<T>{0, 0};
			auto r2 = div(rem2, c);
			ra.quot = ra.quot * rb.quot * c + ra.quot * rb.rem + ra.rem * rb.quot + r2.quot;
			ra.rem = r2.rem;
			return ra;
		}
	} // namespace _floatint

	template <typename REP = void, intmax_t... DEN> requires((std::integral<REP> && sizeof...(DEN) <= 2) || (std::is_void_v<REP> && sizeof...(DEN) == 0))
	struct FloatInt;

	template <>
	struct FloatInt<void>
	{
		template <bool WithSign = true, bool... IsSigned> requires(WithSign ? sizeof...(IsSigned) <= 1 : sizeof...(IsSigned) == 0)
		static constexpr std::pair<const char *, const char *> CheckFormat(const char *begin, const char *end) noexcept;

		template <bool WithSign = true, bool... IsSigned> requires(WithSign ? sizeof...(IsSigned) <= 1 : sizeof...(IsSigned) == 0)
		static constexpr std::pair<const char *, const char *> CheckFormat(std::string_view text) noexcept { return CheckFormat<WithSign, IsSigned...>(text.begin(), text.end()); }

		template <std::unsigned_integral REP, bool WithSign = true>
		static constexpr REP FastConvert(const char *begin, const char *iend, const char *pend, size_t decimalBits) noexcept;

		template <std::signed_integral REP, bool WithSign = true>
		static constexpr REP FastConvert(const char *begin, const char *iend, const char *pend, size_t decimalBits) noexcept;

		template <std::integral REP, bool WithSign = true>
		static constexpr REP FastConvert(const char *begin, const char *end, size_t decimalBits) noexcept { return FastConvert<REP, WithSign>(begin, std::find(begin, end, '.'), end, decimalBits); }

		template <std::integral REP, bool WithSign = true>
		static constexpr REP FastConvert(std::string_view text, size_t decimalBits) noexcept { return FastConvert<REP, WithSign>(text.begin(), text.end(), decimalBits); }

		template <std::integral REP>
		static constexpr std::from_chars_result Convert(std::string_view text, size_t decimalBits, REP &out) noexcept;

		template <std::integral REP>
		static constexpr std::from_chars_result Convert(std::string_view text, size_t decimalBits, REP &out, REP max, REP min = std::numeric_limits<REP>::min()) noexcept;

		template <std::integral REP>
		struct TRatio
		{
			REP den;
			REP num;

		private:
			using SREP = std::make_signed_t<REP>;
			using UREP = std::make_unsigned_t<REP>;

		public:
			static constexpr UREP EMaxRawValue = std::is_signed_v<REP> ? std::numeric_limits<REP>::min() : std::numeric_limits<REP>::max();

			constexpr UREP MaxIntpart() const noexcept
			{
				return num == 1 ? EMaxRawValue / den : _floatint::mulDiv<UREP>(EMaxRawValue, num, den).quot;
			};
			constexpr auto MaxIntpartDigits10() const noexcept { return Math::LogCeil(10, MaxIntpart()); };
			explicit constexpr operator bool() const noexcept { return den != 0; }

			constexpr TRatio() noexcept : den(0), num(0) {}
			constexpr TRatio(REP den, REP num = 1) noexcept : den(den), num(num) { pw_pbl_assert(num >= 1 && den >= num); }
			template <std::integral OTH>
			constexpr TRatio(TRatio<OTH> oth) noexcept : den(oth.den), num(oth.num) { pw_pbl_assert(std::in_range<REP>(oth.den) && std::in_range<REP>(oth.num)); }

			constexpr TRatio<SREP> ToSigned() const noexcept
			{
				pw_pbl_assert(std::in_range<SREP>(den) && std::in_range<SREP>(num));
				return {static_cast<SREP>(den), static_cast<SREP>(num)};
			}
			constexpr TRatio<UREP> ToUnsigned() const noexcept
			{
				pw_pbl_assert(std::in_range<UREP>(den) && std::in_range<UREP>(num));
				return {den, num};
			}

			constexpr std::from_chars_result Convert(std::string_view text, REP &out, REP max = std::numeric_limits<REP>::max(), REP min = std::numeric_limits<REP>::min()) const noexcept;
		};

	private:
		template <std::integral REP>
		static constexpr std::from_chars_result Convert(REP num, REP den, REP max, std::string_view text, REP &out) noexcept;
	};

	template <bool WithSign, bool... IsSigned> requires(WithSign ? sizeof...(IsSigned) <= 1 : sizeof...(IsSigned) == 0)
	constexpr std::pair<const char *, const char *> FloatInt<>::CheckFormat(const char *begin, const char *end) noexcept
	{
		if constexpr (WithSign)
		{
			const char *dend = nullptr;
			if (end <= begin)
				return {begin, dend};
			if constexpr ((IsSigned && ...))
			{
				if (auto sn = *begin; sn != '-' && sn != '+' && !std::isdigit(sn))
					return {begin, dend};
			}
			else
			{
				if (auto sn = *begin; sn != '+' && !std::isdigit(sn))
					return {begin, dend};
			}
			return CheckFormat<false>(begin + 1, end);
		}
		else
		{
			auto iend = std::find_if_not(begin, end, isdigit);
			if (iend >= end - 1 || *iend != '.')
				return {iend, nullptr};
			return {iend, std::find_if_not(iend + 1, end, isdigit)};
		}
	}

	template <std::unsigned_integral REP, bool WithSign>
	constexpr REP FloatInt<>::FastConvert(const char *begin, const char *iend, const char *dend, size_t decimalBits) noexcept
	{
		if constexpr (sizeof(REP) <= sizeof(size_t) && !std::same_as<REP, size_t>)
			return FastConvert<size_t>(begin, iend, dend, decimalBits);
		else if constexpr (WithSign)
		{
			CharConv::RemoveSign(begin, iend);
			return FastConvert<REP, false>(begin, iend, dend, decimalBits);
		}
		else
		{
			auto result = CharConv::fast_from_char<REP, false>(begin, iend) * Math::Exp<REP(10)>(decimalBits);
			if (dend > ++iend)
			{
				if (size_t dsize = dend - iend; dsize == decimalBits)
					result += CharConv::fast_from_char<REP, false>(iend, dend);
				else if (dsize < decimalBits)
					result += CharConv::fast_from_char<REP, false>(iend, dend) * Math::Exp<REP(10)>(decimalBits - dsize);
				else
				{
					auto v = CharConv::fast_from_char<REP, false>(iend, iend + decimalBits);
					if (auto ch = *(iend + decimalBits); ch >= '5' && ch <= '9')
						v++;
					result += v;
				}
			}
			return result;
		}
	}

	template <std::signed_integral REP, bool WithSign>
	constexpr REP FloatInt<>::FastConvert(const char *begin, const char *iend, const char *dend, size_t decimalBits) noexcept
	{
		using ssize_t = std::make_signed_t<size_t>;
		if constexpr (sizeof(ssize_t) <= sizeof(ssize_t) && !std::same_as<REP, ssize_t>)
			return FastConvert<ssize_t>(begin, iend, dend, decimalBits);
		else if constexpr (WithSign)
		{
			bool isNeg = CharConv::RemoveSign(begin, iend);
			auto result = FastConvert<std::make_unsigned_t<REP>, false>(begin, iend, dend, decimalBits);
			return isNeg ? -result : result;
		}
		else
			return FastConvert<std::make_unsigned_t<REP>, false>(begin, iend, dend, decimalBits);
	}

	template <std::integral REP>
	constexpr std::from_chars_result FloatInt<>::Convert(std::string_view text, size_t decimalBits, REP &out) noexcept
	{
		if (text.empty())
			return {.ptr = text.begin(), .ec = std::errc::invalid_argument};
		bool isNeg;
		if constexpr (std::is_signed_v<REP>)
			isNeg = text[0] == '-';
		if (text[0] == '+')
			text = text.substr(1);
		auto isize = text.find('.');
		if (isize > text.size())
			isize = text.size();
		REP v;
		auto result = std::from_chars(text.begin(), text.begin() + isize, v);
		if (result.ec != std::errc{})
			return result;
		if (result.ptr != text.begin() + isize)
			return result.ec = std::errc::invalid_argument, result;
		if (auto k = Math::Exp<REP(10)>(decimalBits); k == 0)
			return result.ec = std::errc::invalid_argument, result;
		else if (auto ovl = Math::MulOverflow(k, &v); ovl)
			return result.ec = std::errc::result_out_of_range, result;
		out = v;
		if (isize < text.size() - 1)
		{
			text = text.substr(isize + 1);
			auto calcEnd = text.size() > decimalBits ? text.begin() + decimalBits : text.end();
			auto result = std::from_chars(text.begin(), calcEnd, v);
			if (result.ec != std::errc{})
				return result;
			if (result.ptr != calcEnd)
				return result.ec = std::errc::invalid_argument, result;
			if (text.size() < decimalBits)
				v *= Math::Exp<REP(10)>(decimalBits - text.size()); // 指数比前面小，确定不溢出
			else if (text.size() > decimalBits)
			{
				if (!std::all_of(calcEnd, text.end(), isdigit))
					return result.ec = std::errc::invalid_argument, result;
				else
					result.ptr = text.end();
				if (*calcEnd >= '5' && *calcEnd <= '9')
					v++;
			}
			if (result.ec != std::errc{})
				return result;
			bool overflow;
			if constexpr (std::is_signed_v<REP>)
				overflow = isNeg ? Math::SubOverflow(v, &out) : Math::AddOverflow(v, &out);
			else
				overflow = Math::AddOverflow(v, &out);
			if (overflow)
				return result.ec = std::errc::result_out_of_range, result;
		}
		return result;
	}
	template <std::integral REP>
	constexpr std::from_chars_result FloatInt<>::Convert(std::string_view text, size_t decimalBits, REP &out, REP max, REP min) noexcept
	{
		auto result = Convert(text, decimalBits, &out);
		if (result.ec != std::errc{})
			return result;
		if (out < min || out > max)
			return result.ec = std::errc::result_out_of_range, result;
		return result;
	}

	template <std::integral REP>
	constexpr std::from_chars_result FloatInt<>::Convert(REP num, REP den, REP max, std::string_view text, REP &out) noexcept
	{
		const char *point = nullptr;
		for (auto p = text.begin(); p <= text.end(); p++)
			if (*p == '.')
			{
				if (point != nullptr)
					return {.ptr = text.begin(), .ec = std::errc::invalid_argument};
				else
					point = p;
			}
		using TUnsigned = std::make_unsigned_t<REP>;
		TUnsigned quot = 0;
		if (*text.begin() == '+')
			text = text.substr(1);
		auto result = std::from_chars(text.begin(), point ? point : text.end(), quot);
		if (result.ec != std::errc{})
			return result;
		if (quot > (num == 1 ? max / den : _floatint::mulDiv(max, num, den).quot)) // 确保下面bigMulDiv结果不会溢出
			return result.ec = std::errc::result_out_of_range, result;
		TUnsigned rem;
		if (quot == 0)
			rem = 0;
		else if (num == 1)
			quot *= den, rem = 0;
		else
		{
			auto re = _floatint::bigMulDiv<TUnsigned>(quot, den, num);
			if (re.quot == 0 && re.rem == 0)
				return result.ec = std::errc::result_out_of_range, result;
			quot = re.quot, rem = re.rem;
		}
		if (point != nullptr)
		{
			point++;
			TUnsigned f = 0;
			result = std::from_chars(point, text.end(), f);
			if (result.ec != std::errc{})
				return result.ec == std::errc::result_out_of_range ? result : std::from_chars_result{.ptr = text.begin(), .ec = result.ec};
			auto dd = Math::Exp<REP(10)>(result.ptr - point);
			if (dd == 0)
				return result.ec = std::errc::result_out_of_range, result;
			auto fcalc = [=, &result, &quot](auto f) {
				auto re = f / dd * den;
				if (num != 1)
					re = (re + rem) / num;
				if (!std::isfinite(re) || Math::AddOverflow((TUnsigned)std::round(re), &quot) || quot > max)
					result.ec = std::errc::result_out_of_range;
			};
			dd <= std::numeric_limits<float>::digits10 ? fcalc(float(f)) : fcalc(double(f));
			if (result.ec != std::errc{})
				return result;
		}
		return out = quot, result;
	}

	template <std::integral REP>
	constexpr std::from_chars_result FloatInt<>::TRatio<REP>::Convert(std::string_view text, REP &out, REP max, REP min) const noexcept
	{
		using TUnsigned = std::make_unsigned_t<REP>;
		using TCalc = std::conditional_t<sizeof(REP) <= sizeof(uintptr_t), uintptr_t, TUnsigned>;
		if constexpr (std::same_as<TCalc, REP>)
			return FloatInt<>::Convert<TCalc>(num, den, max, text, out);
		else
		{
			bool isNeg;
			if constexpr (std::is_signed_v<REP>)
			{
				if (*text.begin() == '-')
					isNeg = true, text = text.substr(1);
				else
					isNeg = false;
			}
			TCalc calc;
			auto result = FloatInt<>::Convert<TCalc>(num, den, std::max(TUnsigned(std::abs(min)), TUnsigned(std::abs(max))), text, calc);
			if (result.ec != std::errc{})
				return result;
			if constexpr (std::is_signed_v<REP>)
				if (isNeg)
					calc = -calc;
			if ((REP &)calc < min || (REP &)calc > max)
				return result.ec = std::errc::result_out_of_range, result;
			return out = calc, result;
		}
	}

	/// 定系数浮点整形，将实际值为 a + b * (ratio.num/ratio.den)的数值存储为 a * (ratio.den/ratio.num) + b 的整形
	/// @tparam REP 存储值的整形类型
	template <std::integral REP>
	struct FloatInt<REP>
	{
		using TRep = REP;
		using TUnsigned = std::make_unsigned_t<TRep>;
		using TRatio = FloatInt<>::TRatio<TRep>;
		static constexpr auto EMaxIntpartDigits10 = std::numeric_limits<TRep>::digits10;

		TRep raw;
		TRatio ratio;

		constexpr FloatInt Min() const noexcept { return {.raw = std::numeric_limits<TRep>::min(), .ratio = ratio}; }
		constexpr FloatInt Max() const noexcept { return {.raw = std::numeric_limits<TRep>::max(), .ratio = ratio}; }

		constexpr auto DefaultPrecision() const { return Math::LogCeil(10, _floatint::divRound(ratio.den, ratio.num)); };
		constexpr auto MaxIntpartDigits10() const { return ratio.MaxIntpartDigits10(); };
		constexpr auto DivDen(TRep v) const noexcept { return _floatint::div(v, ratio.den); }

		constexpr auto Get() const noexcept { return _floatint::mulDiv(raw, ratio.num, ratio.den); }
		constexpr FloatInt &Set(TRep intPart, TRep decPart) noexcept
		{
			if (ratio.num == 1)
			{
				if (Math::MulOverflow(intPart, ratio.den, &raw))
					return raw = 0, *this;
				if (Math::AddOverflow(decPart, &raw))
					return raw = 0, *this;
			}
			else if ((TUnsigned)std::abs(intPart) > ratio.MaxIntpart())
				return raw = 0, *this;
			else
			{
				raw = _floatint::bigMulDiv(intPart, ratio.den, ratio.num).quot;
				if (Math::AddOverflow(decPart, &raw))
					return raw = 0, *this;
			}
			return *this;
		}
		constexpr FloatInt<TUnsigned> Abs() const noexcept
		{
			if constexpr (std::is_unsigned_v<TRep>)
				return *this;
			else
				return {.raw{(TUnsigned)std::abs(raw)}, .ratio = ratio};
		}

		template <std::floating_point T>
		explicit constexpr operator T() const noexcept { return raw * (ratio.num / T{ratio.den}); }

		constexpr std::from_chars_result SetFrom(std::string_view text) noexcept { return ratio.Convert(text, raw); }

		constexpr std::from_chars_result Convert(std::string_view text, TRep &out) const noexcept { return ratio.Convert(text, out); }
	};

	/// 定系数浮点整形，将实际值为 a + b * (NUM/DEN)的数值存储为 a * (DEN/NUM) + b 的整形
	/// @tparam REP 存储值的整形类型
	/// @tparam DEN 系数的分子
	/// @tparam NUM 系数的分母，不指定时默认为１
	template <std::integral REP, intmax_t DEN, intmax_t... NUM> requires(sizeof...(NUM) <= 1)
	struct FloatInt<REP, DEN, NUM...>
	{
		using TRep = REP;
		using TRatio = FloatInt<>::TRatio<TRep>;
		static constexpr TRep ENum{sizeof...(NUM) == 0 ? 1 : (NUM + ... + 0)};
		static constexpr TRep EDen{DEN};
		static_assert(ENum >= 1 && EDen > ENum);
		static constexpr auto ERatio = TRatio{EDen, ENum};
		static constexpr auto EMaxIntpartDigits10 = ERatio.MaxIntpartDigits10();

		TRep raw;

		static constexpr FloatInt Min() noexcept { return {.raw = std::numeric_limits<TRep>::min()}; }
		static constexpr FloatInt Max() noexcept { return {.raw = std::numeric_limits<TRep>::max()}; }

		static constexpr auto DefaultPrecision() { return MetaAuto<Math::LogCeil(10, _floatint::divRound(EDen, ENum))>; };
		static constexpr auto MaxIntpartDigits10() { return EMaxIntpartDigits10; };
		static constexpr auto DivDen(TRep v) noexcept { return _floatint::div(v, EDen); }

		constexpr auto Get() const noexcept
		{
			if constexpr (ENum == 1)
				return _floatint::div(raw, EDen);
			else
				return _floatint::mulDiv(raw, ENum, EDen);
		}
		constexpr FloatInt &Set(TRep intPart, TRep decPart) noexcept
		{
			FloatInt<TRep>{.raw = raw, .ratio{EDen, ENum}}.Set(intPart, decPart).raw;
			return *this;
		}
		constexpr FloatInt<std::make_unsigned_t<TRep>, DEN, NUM...> Abs() const noexcept
		{
			if constexpr (std::is_unsigned_v<TRep>)
				return *this;
			else
				return {.raw{(std::make_unsigned_t<TRep>)Math::Abs(raw)}};
		}

		template <std::floating_point T>
		explicit constexpr operator T() const noexcept
		{
			if constexpr (ENum == 1)
				return raw / T{EDen};
			else
				return raw * (ENum / T{EDen});
		}

		constexpr std::from_chars_result SetFrom(std::string_view text) noexcept { return ERatio.Convert(text, raw); }

		static constexpr std::from_chars_result Convert(std::string_view text, TRep &out) noexcept { return ERatio.Convert(text, out); }
	};
} // namespace pw_pbl

#endif // PW_PBL_FLOATINT_HPP
